#include "ljsonc_create.h"
#include "ljsonc_type.h"
#include "str_buffer.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static bool s_save_value(FILE *fp, const ljsonc_value *value, int deep, bool isIndependent);
static bool s_save_element(FILE *fp, const ljsonc_element *elem, int deep, bool isIndependent);
static bool s_save_json(FILE *fp, const ljsonc_json *json, int deep, bool isIndependent);
static bool s_save_array(FILE *fp, const ljsonc_array *array, int deep, bool isIndependent);
static bool s_save_key(FILE *fp, const char *key, int deep, bool isIndependent);
static bool s_save_tab(FILE *fp, int n);

static void s_get_key(str_buffer *buf, const char *key);
static void s_get_value(str_buffer *buf, const ljsonc_value *value);
static void s_get_element(str_buffer *buf, const ljsonc_element *elem);
static void s_get_json(str_buffer *buf, const ljsonc_json *json);
static void s_get_array(str_buffer *buf, const ljsonc_array *array);

ljsonc_value *create_value()
{
    ljsonc_value *value = (ljsonc_value *)malloc(sizeof(ljsonc_value));
    
    if (value) {
        value->valuetype = CJSON_VALUE_COUNT;
        value->value.str_value = NULL;
    }

    return value;
}

ljsonc_element *create_element()
{
    ljsonc_element *elem = (ljsonc_element *)malloc(sizeof(ljsonc_element));
    if (elem) {
        elem->key = NULL;
        elem->value = NULL;
    }

    return elem;
}

ljsonc_element *create_element_v(const char *key, ljsonc_value *value)
{
    ljsonc_element *elem = (ljsonc_element *)malloc(sizeof(ljsonc_element));
    
    if (elem) {
        elem->key = clone_string(key);
        elem->value = value;
    }
    
    return elem;
}

ljsonc_json *create_json()
{
    ljsonc_json *json = (ljsonc_json *)malloc(sizeof(ljsonc_json));
    if (json) {
        ljsonc_element **p = (ljsonc_element **)malloc(sizeof(ljsonc_element *) * INIT_SIZE);
        json->values = p;
        json->size = 0;
        json->capacity = p ? INIT_SIZE : 0;
    }

    return json;
}

ljsonc_array *create_array()
{
    ljsonc_array *array = (ljsonc_array *)malloc(sizeof(ljsonc_array));

    if (array) {
        ljsonc_value **p = (ljsonc_value **)malloc(sizeof(ljsonc_value*) * INIT_SIZE);
        array->values = p;
        array->size = 0;
        array->capacity = p ? INIT_SIZE : 0;
    }

    return array;
}

CString create_string(const char *c_str)
{
    char *p = NULL;
    if (c_str) {
        int len = strlen(c_str);
        int size = sizeof(char) *(len + 1);
        p = (char *)malloc(size);
        memcpy(p, c_str, size);
        p[len] = '\0';
    }
    return p;
}

ljsonc_value *clone_value(ljsonc_value *value)
{
    ljsonc_value *p_v = NULL;
    
    if (value) {
        p_v = (ljsonc_value *)malloc(sizeof(ljsonc_value));
        
        if (p_v) {
            switch(value->valuetype) {
                case CJSON_VALUE_INT:
                    p_v->value.int_value = value->value.int_value;
                    break;
                    
                case CJSON_VALUE_DOUBLE:
                    p_v->value.df_value = value->value.df_value;
                    break;
                    
                case CJSON_VALUE_STRING:
                    p_v->value.str_value = clone_string(value->value.str_value);
                    break;
                    
                case CJSON_VALUE_JSON:
                    p_v->value.json_value = clone_json(value->value.json_value);
                    break;
                    
                case CJSON_VALUE_ARRAY:
                    p_v->value.array_value = clone_array(value->value.array_value);
                    break;
                    
                case CJSON_VALUE_COUNT:
                    p_v->value.str_value = NULL;
                    break;
                    
                default: break;
            }
            
            p_v->valuetype = value->valuetype;
        }
    }
    
    return p_v;    
}

ljsonc_element *clone_element(ljsonc_element *elem)
{
    ljsonc_element *p = create_element();
    if (p) {
        p->key = clone_string(elem->key);
        p->value = clone_value(elem->value);
    }
    
    return p;
}

ljsonc_json *clone_json(ljsonc_json *json)
{
    ljsonc_json *p_j = NULL;
    
    if (json) {
        p_j = (ljsonc_json *)malloc(sizeof(ljsonc_json));
    
        if (p_j) {
         
            ljsonc_element **p_e = (ljsonc_element **)malloc(sizeof(ljsonc_element *)                           * json->capacity);
            if (p_e) {
                for (int i = 0; i < json->size; ++i) {
                    p_e[i] = clone_element(json->values[i]);
                }
            
                p_j->size = json->size;
                p_j->capacity = json->capacity;
                p_j->values = p_e;
            }
        }
    }
    
    return p_j;
}

ljsonc_array *clone_array(ljsonc_array *array)
{
    ljsonc_array *p_a = NULL;
    
    if (array) {
        p_a = (ljsonc_array *)malloc(sizeof(ljsonc_array));
        
        if (p_a) {
            ljsonc_value **p_v = (ljsonc_value **)malloc(sizeof(ljsonc_value *) * array->capacity);
            
            if (p_v) {
                for (int i = 0; i < array->size; ++i) {
                    p_v[i] = clone_value(array->values[i]);
                }
                
                p_a->size = array->size;
                p_a->capacity = array->capacity;
                p_a->values = p_v;
            }
        }
    }
    
    return p_a;                
}

CString clone_string(const char *c_str)
{
    char *p = NULL;
    
    if (c_str) {
        int len = strlen(c_str);
        int size = sizeof(char) *(len + 1);
        p = (char *)malloc(size);
        memcpy(p, c_str, size);
        p[len] = '\0';
    }
    
    return p;
}

ljsonc_value *create_value_int(int n)
{
    ljsonc_value *value = (ljsonc_value *)malloc(sizeof(ljsonc_value));
    
    if (value) {
        value->valuetype = CJSON_VALUE_INT;
        value->value.int_value = n;
    }
    
    return value;
}

ljsonc_value *create_value_double(double dn)
{
    ljsonc_value *value = (ljsonc_value *)malloc(sizeof(ljsonc_value));
    
    if (value) {
        value->valuetype = CJSON_VALUE_DOUBLE;
        value->value.df_value = dn;
    }
    
    return value;
}

ljsonc_value *create_value_string(const char *c_str)
{
    ljsonc_value *value = (ljsonc_value *)malloc(sizeof(ljsonc_value));
    
    if (value) {
        value->valuetype = CJSON_VALUE_STRING;
        value->value.str_value = clone_string(c_str);
    }
    
    return value;
}

ljsonc_value *create_value_json(ljsonc_json *json)
{
    ljsonc_value *value = (ljsonc_value *)malloc(sizeof(ljsonc_value));
    
    if (value) {
        value->valuetype = CJSON_VALUE_JSON;
        value->value.json_value = json;
    }
    
    return value;
}

ljsonc_value *create_value_array(ljsonc_array *array)
{
    ljsonc_value *value = (ljsonc_value *)malloc(sizeof(ljsonc_value));
    
    if (value) {
        value->valuetype = CJSON_VALUE_ARRAY;
        value->value.array_value = array;
    }
    
    return value;
}

void destory_value(ljsonc_value *value)
{
    if (value) {
        switch(value->valuetype) {
            case CJSON_VALUE_STRING: {
                    destory_string(value->value.str_value);
                }
                break;
            case CJSON_VALUE_JSON: {
                    destory_json(value->value.json_value);
                }
                break;
            case CJSON_VALUE_ARRAY: {
                    destory_array(value->value.array_value);
                }
                break;
            default: break;
        }

        free(value);
    }
}

void destory_element(ljsonc_element *elem)
{
    if (elem) {
        destory_string(elem->key);
        destory_value(elem->value);
        free(elem);
    }
}

void destory_json(ljsonc_json *json)
{
    if (json) {
        for (int i = 0; i < json->size; ++i) {
            destory_element(json->values[i]);
        }

        free(json);
    }
}

void destory_array(ljsonc_array *array)
{
    if (array) {
        for (int i = 0; i < array->size; ++i) {
            destory_value(array->values[i]);
        }

        free(array);
    }
}

void destory_string(CString str)
{
    free(str);
}

void set_value_int(ljsonc_value *value, int n)
{
    value->valuetype = CJSON_VALUE_INT;
    value->value.int_value = n;
}

void set_value_double(ljsonc_value *value, double dn)
{
    value->valuetype = CJSON_VALUE_DOUBLE;
    value->value.df_value = dn;
}

void set_value_string(ljsonc_value *value, const char *c_str)
{
    value->valuetype = CJSON_VALUE_STRING;
    
    if (value->value.str_value) {
        destory_string(value->value.str_value);
    }
    
    value->value.str_value = create_string(c_str);
}

void set_value_json(ljsonc_value *value, ljsonc_json *json)
{
    value->valuetype = CJSON_VALUE_JSON;
    
    if (value->value.json_value) {
        destory_json(value->value.json_value);
    }
    
    value->value.json_value = json;
}

void set_value_array(ljsonc_value *value, ljsonc_array *array)
{
    value->valuetype = CJSON_VALUE_ARRAY;
    
    if (value->value.array_value) {
        destory_array(value->value.array_value);
    }
    
    value->value.array_value = array;
}

void set_elem_key(ljsonc_element *elem, const char *c_str)
{
    if (elem->key) {
        destory_string(elem->key);
    }

    elem->key = create_string(c_str);
}

void set_elem_value(ljsonc_element *elem, ljsonc_value *value)
{
    if (elem->value) {
        destory_value(elem->value);
    }

    elem->value = value;
}

void set_element(ljsonc_element *elem, const char *c_str, ljsonc_value *value)
{
    if (elem->key) {
        destory_string(elem->key);
    }

    elem->key = create_string(c_str);
    
    if (elem->value) {
        destory_value(elem->value);
    }

    elem->value = value;
}

void set_element_int(ljsonc_element *elem, int n)
{
    if (elem) {
        switch (elem->value->valuetype) {
            case CJSON_VALUE_INT:
            case CJSON_VALUE_DOUBLE:
                break;
            
            case CJSON_VALUE_STRING:
                destory_string(elem->value->value.str_value);
                break;
                
            case CJSON_VALUE_JSON:
                destory_json(elem->value->value.json_value);
                break;
                
            case CJSON_VALUE_ARRAY:
                destory_array(elem->value->value.array_value);
                break;
                
            default: break;
        }
        
        elem->value->valuetype = CJSON_VALUE_INT;
        elem->value->value.int_value = n;
    }
}
        
void set_element_double(ljsonc_element *elem, double dn)
{
    if (elem) {
        switch (elem->value->valuetype) {
            case CJSON_VALUE_INT:
            case CJSON_VALUE_DOUBLE:
            break;
                
            case CJSON_VALUE_STRING:
                destory_string(elem->value->value.str_value);
                break;
                    
            case CJSON_VALUE_JSON:
                destory_json(elem->value->value.json_value);
                break;
                    
            case CJSON_VALUE_ARRAY:
                destory_array(elem->value->value.array_value);
                break;
                    
            default: break;
        }
   
        elem->value->valuetype = CJSON_VALUE_DOUBLE;
        elem->value->value.df_value = dn;
    }
}

void set_element_string(ljsonc_element *elem, const char *c_str)
{
    if (elem) {
        switch (elem->value->valuetype) {
            case CJSON_VALUE_INT:
            case CJSON_VALUE_DOUBLE:
                break;
            
            case CJSON_VALUE_STRING:
                destory_string(elem->value->value.str_value);
                break;
                    
            case CJSON_VALUE_JSON:
                destory_json(elem->value->value.json_value);
                break;
                    
            case CJSON_VALUE_ARRAY:
                destory_array(elem->value->value.array_value);
                break;
                    
            default: break;
        }
                
        elem->value->valuetype = CJSON_VALUE_STRING;
        elem->value->value.str_value = create_string(c_str);
    }
}

void set_element_json(ljsonc_element *elem, ljsonc_json *json)
{
    if (elem) {
        switch (elem->value->valuetype) {
            case CJSON_VALUE_INT:
            case CJSON_VALUE_DOUBLE:
                break;
                
            case CJSON_VALUE_STRING:
                destory_string(elem->value->value.str_value);
                break;
                    
            case CJSON_VALUE_JSON:
                destory_json(elem->value->value.json_value);
                break;
                    
            case CJSON_VALUE_ARRAY:
                destory_array(elem->value->value.array_value);
                break;
                    
            default: break;
        }
        
        elem->value->valuetype = CJSON_VALUE_JSON;
        elem->value->value.json_value = json;
    }
}

void set_element_array(ljsonc_element *elem, ljsonc_array *array)
{
    if (elem) {
        switch (elem->value->valuetype) {
            case CJSON_VALUE_INT:
            case CJSON_VALUE_DOUBLE:
                break;
                
            case CJSON_VALUE_STRING:
                destory_string(elem->value->value.str_value);
                break;
                    
            case CJSON_VALUE_JSON:
                destory_json(elem->value->value.json_value);
                break;
                    
            case CJSON_VALUE_ARRAY:
                destory_array(elem->value->value.array_value);
                break;
                    
            default: break;
        }
        
        elem->value->valuetype = CJSON_VALUE_ARRAY;
        elem->value->value.array_value = array;
    }
}

bool json_append(ljsonc_json *json, ljsonc_element *elem)
{
    if (json->size == json->capacity) {
        ljsonc_element **p = (ljsonc_element **)realloc(json->values, 
                                                        sizeof (ljsonc_element *) 
                                                        * (json->capacity + INCREASE_SIZE));
        if (!p) {
            return false;
        }
        json->values = p;
        json->capacity += INCREASE_SIZE;
    }

    json->values[json->size++] = elem;
    
    return true;
}

bool array_append(ljsonc_array *array, ljsonc_value *value)
{
        if (array->size == array->capacity) {
        ljsonc_value **p = (ljsonc_value **)realloc(array->values, 
                                                    sizeof (ljsonc_value *) 
                                                    * (array->capacity + INCREASE_SIZE));
       
        if (!p) {
            return false;
        }
        array->values = p;
        array->capacity += INCREASE_SIZE;
    }

    array->values[array->size++] = value;
    
    return true;    
}

static inline 
bool s_save_tab(FILE *fp, int n)
{
    for (int i = 0; i < n; ++i) {
        fputs("\t",fp);
    }

    return true;
}

inline static
bool s_save_key(FILE *fp, const char *key, int deep, bool isIndependent)
{
    fprintf(fp, "\"%s\":", key);

    return true;
}

static inline 
bool s_save_value(FILE *fp, const ljsonc_value *value, int deep, bool isIndependent)
{
    switch(value->valuetype) {  
        case CJSON_VALUE_INT:
            if (isIndependent) {
                s_save_tab(fp, deep);
            }

            fprintf(fp, "%d", value->value.int_value);
            break;
            
        case CJSON_VALUE_DOUBLE:
            if (isIndependent) {
                s_save_tab(fp, deep);
            }

            fprintf(fp, "%f", value->value.df_value);
            break;
            
        case CJSON_VALUE_STRING:
            if (isIndependent) {
                s_save_tab(fp, deep);
            }
            
            fprintf(fp, "\"%s\"", value->value.str_value);
            break;
            
        case CJSON_VALUE_JSON:
            s_save_json(fp, value->value.json_value, deep, isIndependent);
            break;
        case CJSON_VALUE_ARRAY:
            s_save_array(fp, value->value.array_value, deep, isIndependent);
            break;
        default: return false;
    }
    
    return true;
}

static inline 
bool s_save_element(FILE *fp, const ljsonc_element *elem, int deep, bool isIndependent)
{
    s_save_tab(fp, deep);

    fprintf(fp, "\"%s\":", elem->key);
    s_save_value(fp, elem->value, deep, false);

    return true;
}

static inline 
bool s_save_json(FILE *fp, const ljsonc_json *json, int deep, bool isIndependent)
{
    if (isIndependent) {
        s_save_tab(fp, deep);
    }

    fputs("{\n", fp);

    for (int i = 0; i < json->size - 1; ++i) {
        s_save_element(fp, json->values[i], deep + 1, true);
        fputs(",\n", fp);
    }
    
    s_save_element(fp, json->values[json->size - 1], deep + 1, true);
    fputs("\n", fp);
    s_save_tab(fp, deep);
    fputs("}", fp);

    return true;
}

static inline 
bool s_save_array(FILE *fp, const ljsonc_array *array, int deep, bool isIndependent)
{
    if (isIndependent) {
        s_save_tab(fp, deep);
    }
    
    fputs("[\n", fp);
  
    for (int i = 0; i < array->size - 1; ++i) {
        s_save_value(fp, array->values[i], deep + 1, true);
        fputs(",\n", fp);
    }
    
    s_save_value(fp, array->values[array->size - 1], deep + 1, true);
    fputs("\n", fp);
    s_save_tab(fp, deep);
    fputs("]",fp);

    return true; 
}

bool save_json(FILE *fp, ljsonc_json *json)
{
    if (fp) {
        return s_save_json(fp, json, 0, true);
    }
    else {
        return false;
    }    
}

inline static
void s_get_key(str_buffer *buf, const char *key)
{
    sprintf(get_buffer_end(buf), "\"%s\":", key);
    update_buffer(buf);
}

inline static 
void s_get_value(str_buffer *buf, const ljsonc_value *value)
{
    switch(value->valuetype) {
        case CJSON_VALUE_INT:
            sprintf(get_buffer_end(buf), "%d", value->value.int_value);
            break;
            
        case CJSON_VALUE_DOUBLE:
            sprintf(get_buffer_end(buf), "%f", value->value.df_value);
            break;
            
        case CJSON_VALUE_STRING:
            sprintf(get_buffer_end(buf), "\"%s\"", value->value.str_value);
            break;
            
        case CJSON_VALUE_JSON:
            s_get_json(buf, value->value.json_value);
            break;
        case CJSON_VALUE_ARRAY:
            s_get_array(buf, value->value.array_value);
            break;
        default: break;
    }
    
    update_buffer(buf);
}

inline static 
void s_get_element(str_buffer *buf, const ljsonc_element *elem)
{
    sprintf(get_buffer_end(buf), "\"%s\":", elem->key);
    update_buffer(buf);
    
    s_get_value(buf, elem->value);
}

inline static
void s_get_json(str_buffer *buf, const ljsonc_json *json)
{
    sprintf(get_buffer_end(buf), "{");
    update_buffer(buf);

    for (int i = 0; i < json->size - 1; ++i) {
        s_get_element(buf, json->values[i]);
        sprintf(get_buffer_end(buf), ",");
        update_buffer(buf);
    }
    
    s_get_element(buf, json->values[json->size - 1]);
    
    sprintf(get_buffer_end(buf), "}");
    update_buffer(buf);
}

inline static
void s_get_array(str_buffer *buf, const ljsonc_array *array)
{
    sprintf(get_buffer_end(buf), "[");
    update_buffer(buf);
  
    for (int i = 0; i < array->size - 1; ++i) {
        s_get_value(buf, array->values[i]);
        sprintf(get_buffer_end(buf), ",");
        update_buffer(buf);
    }
    
    s_get_value(buf, array->values[array->size - 1]);

    sprintf(get_buffer_end(buf), "]");
    update_buffer(buf);
}

CString get_json_value(ljsonc_json *json)
{
    str_buffer *buf = create_buffer(BUF_SIZE);
    
    if (json) {
        s_get_json(buf, json);
    }
    
    return get_buffer_value(buf);    
}